#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include "lib/scheduler.h"

// Some globals with widget size information
gint window_w = 640;
gint window_h = 360;
gint global_table_h = 60;
gint statusbar_h = 23;

// Operation running
gboolean running;

// Wrap for passing widgets in evetns
typedef struct {
    GtkWidget *window;
    GtkWidget *global_table;
    GtkWidget *fixed_layout;
    GtkWidget *operation_mode_combobox;
    GtkWidget *thread_count_spinbox;
    GtkWidget *quantum_spinbox;
    GtkWidget *continue_button;
    GtkWidget *start_button;
    GtkWidget *quit_button;
    GtkWidget *scheduler_statusbar;
    GtkWidget *threads_scroll_window;
    GtkWidget *thread_table;
    GPtrArray *thread_progressbars;
    GPtrArray *thread_ticket_spinboxes;
    GPtrArray *thread_work_spinboxes;
    glong thread_count;
} update_widgets_info;

// DIE!!!!
void Destroy(GtkWidget *widget, gpointer data) {
	
	update_widgets_info *update_info = (update_widgets_info *) data;
	
	g_ptr_array_free(update_info->thread_progressbars, TRUE);
	g_ptr_array_free(update_info->thread_ticket_spinboxes, TRUE);
	g_ptr_array_free(update_info->thread_work_spinboxes, TRUE);
	
    gtk_main_quit();
}

// Callback associated with Continue button, used for re-building the GUI according to parameters
void Rebuild_GUI(GtkWidget *widget, gpointer data) {
    
    update_widgets_info *update_info = (update_widgets_info *) data;
    
    glong thread_count = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(update_info->thread_count_spinbox));
    update_info->thread_count = thread_count;
    
    GtkWidget *label;
    gpointer *tmp_widget;
    gchar *text;
    
    // Creating inner table
    update_info->thread_table = gtk_table_new(thread_count+1, 4, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(update_info->thread_table), 2);
    gtk_table_set_col_spacings(GTK_TABLE(update_info->thread_table), 2);
    
    // Creating header of the inner table
    label = gtk_label_new("Thread");
    gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), label, 0, 1, 0, 1);
    label = gtk_label_new("Progress");
    gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), label, 1, 2, 0, 1);
    label = gtk_label_new("Tickets");
    gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), label, 2, 3, 0, 1);
    label = gtk_label_new("Work units");
    gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), label, 3, 4, 0, 1);
    
    // Adding each thread's widgets
    for (long int i = 0; i < thread_count; i++) {
        
        text = g_strdup_printf("%ld", i);
        label = gtk_label_new(text);
        g_free(text);
        
        // Thread's number label
        gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), label, 0, 1, i+1, i+2);
        
        // Thread's progressbar
        g_ptr_array_add(update_info->thread_progressbars, (gpointer) gtk_progress_bar_new());
        tmp_widget = g_ptr_array_index(update_info->thread_progressbars, i);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(tmp_widget), "0%");
        gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), (GtkWidget *)tmp_widget, 1, 2, i+1, i+2);
        
        // Thread's ticket number spin button
        g_ptr_array_add(update_info->thread_ticket_spinboxes, (gpointer) gtk_spin_button_new_with_range(0, LONG_MAX, 1)); // FIXME change back to 1
        tmp_widget = g_ptr_array_index(update_info->thread_ticket_spinboxes, i);
        gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(tmp_widget), TRUE);
        gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), (GtkWidget *)tmp_widget, 2, 3, i+1, i+2);
        
        // Thread's work spin button
        g_ptr_array_add(update_info->thread_work_spinboxes, (gpointer) gtk_spin_button_new_with_range(0, LONG_MAX, 1));
        tmp_widget = g_ptr_array_index(update_info->thread_work_spinboxes, i);
        gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(tmp_widget), TRUE);
        gtk_table_attach_defaults(GTK_TABLE(update_info->thread_table), (GtkWidget *)tmp_widget, 3, 4, i+1, i+2);
        
    }
    
    // Making a scroll window to hold the thread table
    update_info->threads_scroll_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(update_info->threads_scroll_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(update_info->threads_scroll_window), GTK_SHADOW_IN);
    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(update_info->threads_scroll_window), update_info->thread_table);
    
    // Getting sizes of other widgets
    gint global_table_w;
    gint global_table_h;
    gtk_widget_get_size_request (update_info->global_table, &global_table_w, &global_table_h);
    gint scheduler_statusbar_w;
    gint scheduler_statusbar_h;
    gtk_widget_get_size_request (update_info->scheduler_statusbar, &scheduler_statusbar_w, &scheduler_statusbar_h);
    gint window_w;
    gint window_h;
    gtk_window_get_size (GTK_WINDOW(update_info->window), &window_w, &window_h);

	// Removing Continue button and adding Start button
	gtk_widget_destroy(widget);
	gtk_table_attach_defaults(GTK_TABLE(update_info->global_table), update_info->start_button, 2, 3, 2, 3);

	// Adding new widgets to window
    gtk_fixed_put(GTK_FIXED(update_info->fixed_layout), update_info->threads_scroll_window, 0, global_table_h+2);
    gtk_widget_set_size_request(update_info->threads_scroll_window, window_w, window_h-(scheduler_statusbar_h+1)-(global_table_h+2));
    
    // Disabling all input widgets
    gtk_widget_set_sensitive(update_info->operation_mode_combobox, FALSE);
    gtk_widget_set_sensitive(update_info->thread_count_spinbox, FALSE);
    gtk_widget_set_sensitive(update_info->quantum_spinbox, FALSE);
    
    // Showing all widgets in window
    gtk_widget_show_all(update_info->window);

}

void runDummyScheduler(configuration *config) {
    
    gpointer *pbar;
    gchar *str;
    gdouble pi = 631568541.351;
    gdouble p = 0.0;    

    long int i = 0;
    long int *total_work_units = malloc (config->threads_num * sizeof(long int*));
    int finished = 0;

    for (long int i = 0; i < config->threads_num; i++) {
        total_work_units[i] = 1234567890;
        config->work_units[i] = 1234567890;
    }


    while (finished < config->threads_num) {
        
        i = rand() % config->threads_num; 
        
        pbar = g_ptr_array_index(config->thread_progressbars, i);
        
        if (config->work_units[i] > 0) {
            
            p = (gdouble) (total_work_units[i] - config->work_units[i]) / (gdouble) total_work_units[i];
            printf("Thread %li at %f\n", i, (double)p);
            pi += p;
            
            gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(pbar), p);
            while (g_main_context_iteration(NULL, FALSE));
            
            str = g_strdup_printf("%.0f%%", p*100);
            gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pbar), str);
            while (g_main_context_iteration(NULL, FALSE));
            g_free(str);
            
            str = g_strdup_printf("Thread %li running, PI value: %f", i, (double)pi);
            gtk_statusbar_push(GTK_STATUSBAR(config->scheduler_statusbar), gtk_statusbar_get_context_id(GTK_STATUSBAR(config->scheduler_statusbar), str), str);
            while (g_main_context_iteration(NULL, FALSE));
            g_free(str);
            
            config->work_units[i] -= 1000000;
         
            if (config->work_units[i] <= 0) {
                finished++;
                str = g_strdup_printf("100%%, PI: %f", (double)pi);
                gtk_progress_bar_set_text(GTK_PROGRESS_BAR(pbar), str);
                while (g_main_context_iteration(NULL, FALSE));
                g_free(str);
            }
            
        }
        
    }
    
    free(total_work_units);
    
}

// Callback associated with Start button
void Start(GtkWidget *widget, gpointer data) {

    update_widgets_info *update_info = (update_widgets_info*) data;
    gchar *str;

	// Disabling widgets
	gtk_widget_set_sensitive(update_info->start_button, FALSE);
	gtk_widget_set_sensitive(update_info->quit_button, FALSE);
	for (long int i = 0; i < update_info->thread_count; i++) {
		gtk_widget_set_sensitive(g_ptr_array_index(update_info->thread_ticket_spinboxes, i), FALSE);
		gtk_widget_set_sensitive(g_ptr_array_index(update_info->thread_work_spinboxes, i), FALSE);
	}

    // Cleaning up the progress bars
    for (long int i = 0; i < update_info->thread_count; i++) {
        gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(g_ptr_array_index(update_info->thread_progressbars, i)), 0.0);
        
        str = g_strdup_printf("%.0f%%", 0.0);
        gtk_progress_bar_set_text(GTK_PROGRESS_BAR(g_ptr_array_index(update_info->thread_progressbars, i)), str);
        while (g_main_context_iteration(NULL, FALSE));
        g_free(str);
    }

	// Creating configuration
	configuration *config = (configuration*) malloc(sizeof(configuration));
	config->scheduler_statusbar = update_info->scheduler_statusbar;
	config->thread_progressbars = update_info->thread_progressbars;
	
	// Setting mode from the GUI
	str =  gtk_combo_box_get_active_text(GTK_COMBO_BOX(update_info->operation_mode_combobox));
    if (!g_strcmp0 (str, "Preemptive")) { // returns 0 if equal
        config->mode = PREEMPTIVE;
    } else {
        config->mode = NON_PREEMPTIVE;
    }
	
	// Setting thread number
	config->threads_num = update_info->thread_count;

	// Setting the quantum
	config->quantum = gtk_spin_button_get_value_as_float(GTK_SPIN_BUTTON(update_info->quantum_spinbox));
	
	// Allocating arrays for threads' data
	config->tickets_num = (long int*) malloc (update_info->thread_count*sizeof(long int));
	config->work_units = (long int*) malloc (update_info->thread_count*sizeof(long int));
	
	// Setting tickets and work units per thread
	for (long int i = 0; i < update_info->thread_count; i++) {
		config->tickets_num[i] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(g_ptr_array_index(update_info->thread_ticket_spinboxes, i)));
		config->work_units[i] = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(g_ptr_array_index(update_info->thread_work_spinboxes, i)));
	}
	
    // Running the scheduler
    running = TRUE;
    runScheduler(config);
    // NOTE Uncomment to runDummyScheduler(config);
    running = FALSE;
    
    // Setting the final status in the status bar (duh!)
    str = g_strdup_printf("Done!");
    gtk_statusbar_push(GTK_STATUSBAR(update_info->scheduler_statusbar), gtk_statusbar_get_context_id(GTK_STATUSBAR(update_info->scheduler_statusbar), str), str);

	// Re-enabling widgets
	//gtk_widget_set_sensitive(update_info->start_button, TRUE);
	gtk_widget_set_sensitive(update_info->quit_button, TRUE);
	//for (int i = 0; i < update_info->thread_count; i++) {
	//	gtk_widget_set_sensitive(g_ptr_array_index(update_info->thread_ticket_spinboxes, i), TRUE);
	//	gtk_widget_set_sensitive(g_ptr_array_index(update_info->thread_work_spinboxes, i), TRUE);
	//}

	printf ("Whew!\n");

}

// Callback associated with mode combobox to change the quantum unit label
void ChangeQuantumUnit(GtkWidget *widget, gpointer label) { 

    gchar *text =  gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
    if (!g_strcmp0 (text, "Preemptive")) { // returns 0 if equal
        gtk_label_set_text(GTK_LABEL(label), "Quantum (ms)");
    } else {
        gtk_label_set_text(GTK_LABEL(label), "Quantum (%)");
    }
    g_free(text);
    
}

// Used to prevent window to be closed when the scheduler is running
int PreventClose(GtkWidget *window, gpointer data) {
	if (running) {
		GtkWidget *dialog;
		dialog = gtk_message_dialog_new(GTK_WINDOW(window),
				GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_WARNING,
				GTK_BUTTONS_OK,
				"Please wait until the scheduler finishes to close the program.");
		gtk_window_set_title(GTK_WINDOW(dialog), "Not allowed!");
		gtk_dialog_run(GTK_DIALOG(dialog));
		gtk_widget_destroy(dialog);
		return TRUE;
	} else {
		gtk_main_quit();
		return FALSE;
	}
}

int main(int argc, char** argv) {

    // Initiating GTK+ Library
    gtk_init(&argc, &argv);

    // Initialize the seed for random nums
    srand(time(NULL));

    // Defining outer widgets
    GtkWidget *window;
    GtkWidget *global_table;
    GtkWidget *fixed_layout;
    GtkWidget *operation_mode_label;
    GtkWidget *operation_mode_combobox;
    GtkWidget *thread_count_label;
    GtkWidget *thread_count_spinbox;
    GtkWidget *quantum_label;
    GtkWidget *quantum_spinbox;
    GtkWidget *continue_button;
    GtkWidget *start_button;
    GtkWidget *quit_button;
    GtkWidget *scheduler_statusbar;
    
    // Defining inner widgets
    GtkWidget *threads_scroll_window = NULL;
    GtkWidget *thread_table = NULL;
    GPtrArray *thread_progressbars;
    GPtrArray *thread_ticket_spinboxes;
    GPtrArray *thread_work_spinboxes;
    
    /* Initializing thread's widget arrays */
    thread_progressbars = g_ptr_array_new();
    thread_ticket_spinboxes = g_ptr_array_new();
    thread_work_spinboxes = g_ptr_array_new();
    
    /* Creating outer widgets */

    // Window
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER_ALWAYS);
    gtk_window_set_default_size(GTK_WINDOW(window), window_w, window_h);
    gtk_window_set_title(GTK_WINDOW(window), "Scheduler");   
    gtk_window_set_resizable (GTK_WINDOW(window), FALSE);
    gtk_window_set_deletable (GTK_WINDOW(window), FALSE);

    // Fixed layout for the window
    fixed_layout = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(window), fixed_layout);

    // Global table to hold all widgets
    global_table = gtk_table_new(3, 4, FALSE);
    gtk_table_set_row_spacings(GTK_TABLE(global_table), 2);
    gtk_table_set_col_spacings(GTK_TABLE(global_table), 2);

    /* Initial form */
    
    // Operation mode
    operation_mode_label = gtk_label_new("Operation mode");
    operation_mode_combobox = gtk_combo_box_text_new();
    gtk_combo_box_append_text(GTK_COMBO_BOX(operation_mode_combobox), "Preemptive");
    gtk_combo_box_append_text(GTK_COMBO_BOX(operation_mode_combobox), "Non-preemptive");
    gtk_combo_box_set_active(GTK_COMBO_BOX(operation_mode_combobox), 1);
    //gtk_combo_box_text_get_active_text 
    
    // Number of threads
    thread_count_label = gtk_label_new("Thread count");
    thread_count_spinbox = gtk_spin_button_new_with_range(5, LONG_MAX, 1.0);
    gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(thread_count_spinbox), TRUE);
    //gtk_spin_button_get_value_as_int
    
    // Quantum amount
    quantum_label = gtk_label_new("Quantum (%)");
    quantum_spinbox = gtk_spin_button_new_with_range(1, DBL_MAX, 1.0);
    gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(quantum_spinbox), FALSE);
    //gtk_spin_button_get_value_as_int
    
    // Continue button
    continue_button = gtk_button_new_with_label("Continue");
    
    // Start button
    start_button = gtk_button_new_with_label("Start");
    gtk_widget_set_sensitive(start_button, TRUE);
    
    // Quit button
    quit_button = gtk_button_new_with_label("Quit");
    gtk_widget_set_sensitive(start_button, TRUE);
    
    // Scheduler's status bar
    scheduler_statusbar = gtk_statusbar_new();
    
    /* Adding inner widgets to global table */
    
    // Form header
    // First column
    gtk_table_attach_defaults(GTK_TABLE(global_table), operation_mode_label, 0, 1, 1, 2);
    gtk_table_attach_defaults(GTK_TABLE(global_table), thread_count_label, 0, 1, 2, 3);
    // Second column
    gtk_table_attach_defaults(GTK_TABLE(global_table), operation_mode_combobox, 1, 2, 1, 2);
    gtk_table_attach_defaults(GTK_TABLE(global_table), thread_count_spinbox, 1, 2, 2, 3);
    // Third column
    gtk_table_attach_defaults(GTK_TABLE(global_table), quantum_label, 2, 3, 1, 2);
    gtk_table_attach_defaults(GTK_TABLE(global_table), quit_button, 3, 4, 2, 3);
    gtk_table_attach_defaults(GTK_TABLE(global_table), continue_button, 2, 3, 2, 3);
    // Fourth column
    gtk_table_attach_defaults(GTK_TABLE(global_table), quantum_spinbox, 3, 4, 1, 2);
    
    // Status bar at the button
    gtk_fixed_put(GTK_FIXED(fixed_layout), scheduler_statusbar, 0, window_h-statusbar_h);
    gtk_widget_set_size_request(scheduler_statusbar, window_w, statusbar_h);
    
    // Adding global table to the window
    gtk_fixed_put(GTK_FIXED(fixed_layout), global_table, 0, 0);
    gtk_widget_set_size_request(global_table, window_w, global_table_h);
    
    // Packing pointers needed to rebuild the GUI
    update_widgets_info update_info;
    update_info.window = window;
    update_info.global_table = global_table;
    update_info.fixed_layout = fixed_layout;
    update_info.operation_mode_combobox = operation_mode_combobox;
    update_info.thread_count_spinbox = thread_count_spinbox;
    update_info.quantum_spinbox = quantum_spinbox;
    update_info.continue_button = continue_button;
    update_info.start_button = start_button;
    update_info.quit_button = quit_button;
    update_info.scheduler_statusbar = scheduler_statusbar;
    update_info.threads_scroll_window = threads_scroll_window;
    update_info.thread_table = thread_table;
    update_info.thread_progressbars = thread_progressbars;
    update_info.thread_ticket_spinboxes = thread_ticket_spinboxes;
    update_info.thread_work_spinboxes = thread_work_spinboxes;
    
    // Connecting signals
    g_signal_connect(G_OBJECT(operation_mode_combobox), "changed", G_CALLBACK(ChangeQuantumUnit), (gpointer) quantum_label);
    g_signal_connect(G_OBJECT(continue_button), "clicked", G_CALLBACK(Rebuild_GUI), (gpointer) &update_info);
    g_signal_connect(G_OBJECT(start_button), "clicked", G_CALLBACK(Start), (gpointer) &update_info);
    g_signal_connect(G_OBJECT(quit_button), "clicked", G_CALLBACK(Destroy), (gpointer) &update_info);
    g_signal_connect(window, "delete-event", G_CALLBACK(PreventClose), NULL);

    // Showing all widgets in window
    gtk_widget_show_all(window);

    // Enter the GTK main loop
    gtk_main();

    return 0;
}
